package com.qf.test.controller;

import com.qf.test.captcha.CaptchaCodeManager;
import com.qf.test.pojo.DtsAdmin;
import com.qf.test.service.PermissionService;
import com.qf.test.service.RoleService;
import com.qf.test.util.*;
import com.qf.test.util.Base64;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*;

import java.io.ByteArrayOutputStream;
import java.util.*;

/**
 * 权限模块contller
 */
//这是一个contller类 交给spring容器管理
// @Controller
//并且这个类中的所有的方法的返回值都会自动转化成json格式返回
@RestController
//解决前后段分离
@CrossOrigin("*")
@Api(value = "权限相关的接口 登录 注销 验证码")
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private ApplicationContext context;

    private HashMap<String,String> systemPermMap=null;

    //打印日志对象
    private static final Logger logger = LoggerFactory.getLogger(AuthController.class);

    /**
     * 获取验证码
     */
    @GetMapping("/captchaImage")
    @ApiOperation(value = "获取验证码图片")
    public Object findCode() throws Exception {
        //1.生成随机的4位字符串作为验证码
        String codeStr = VerifyCodeUtils.generateVerifyCode(4);
        //2.生成验证码的唯一表示
        String uuid = com.qf.test.util.UUID.randomUUID().toString(true);
        //3.验证码存储到内存中
        boolean flag = CaptchaCodeManager.addToCache(uuid, codeStr, 10);
        //4.判断验证码是否能存储成功
        if (!flag) {
            logger.error("生成的验证码出错：{}", AdminResponseCode.AUTH_CAPTCHA_FREQUENCY.desc());
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_FREQUENCY);
        }
        //5.生成验证码图片
        int w = 100;
        int h = 40;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        VerifyCodeUtils.outputImage(w, h, outputStream, codeStr);
        //6 将图片唯一的id图片内容返回
        HashMap<String, Object> data = new HashMap<>();
        data.put("img", Base64.encode(outputStream.toByteArray()));
        data.put("uuid", uuid);
        //返回封装的数据
        return ResponseUtil.ok(data);
    }

    /**
     * 登录
     */
    @RequestMapping("/login")
    public Object login(@RequestBody String body) {
        //1.从传入的josn字符串中提取参数数据
        String code = JacksonUtil.parseString(body, "code");
        String username = JacksonUtil.parseString(body, "username");
        String password = JacksonUtil.parseString(body, "password");
        String uuid = JacksonUtil.parseString(body, "uuid");

        //2.判断验证码 用户名密码是否为空 如果为空返回空信息
        if (StringUtils.isEmpty(code) || StringUtils.isEmpty(username)
                || StringUtils.isEmpty(password) || StringUtils.isEmpty(uuid)) {
            return ResponseUtil.badArgument();
        }
        //3.校验验证码是否正确 如果不正确返回提示信息 让用户重新填写
        //从缓存中获取验证码
        String captcha = CaptchaCodeManager.getCachedCaptcha(uuid);
        //判断验证码是否超时
        if (captcha == null) {
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_EXPIRED);
        }
        //判断验证码是否正确
        if (!captcha.equalsIgnoreCase(code)) {
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_ERROR);

        }

        //4.创建shiro对象的subject对象
        Subject subject = SecurityUtils.getSubject();
        //5.创建shior的用户名密码对象 封装用户页面输入的验证码 密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        //6.调用登录方法 传入的用户名密码对象 就会触发shiro的自定义reaalm进行校验
        try {
            subject.login(usernamePasswordToken);
        } catch (UnknownAccountException e) {
            return AdminResponseUtil.fail(AdminResponseCode.ADMIN_INVALID_ACCOUNT_OR_PASSWORD);
        } catch (AuthenticationException e) {
            return AdminResponseUtil.fail(AdminResponseCode.ADMIN_INVALID_AUTH);

        }
        //7 返回登录信息
        return ResponseUtil.ok(subject.getSession().getId());

    }

    /**
     * 获取用户登录后的详细信息 用户名 角色 权限
     */
    @RequestMapping("/info")
    public Object info() {
        //1.从shior框架中获取当前用户的登录对象
        Subject subject = SecurityUtils.getSubject();
        //2.获取当前登录用户的用户名和用户头像
        DtsAdmin admin = (DtsAdmin) subject.getPrincipal();
        Map<String, Object> data = new HashMap<>();

        data.put("name", admin.getUsername());
        data.put("acater", admin.getAvatar());
        //3.根据当前用户中的角色id数组
        Integer[] roleIds = admin.getRoleIds();
        //4.根据角色id数组到数据库中查询角色集合
        Set<String> roleSet = roleService.queryByIds(roleIds);
        //5.根据角色id数组到数据库中查询权限字符串合集
        Set<String> permset = permissionService.queryPermissonByRoleIds(roleIds);

        //6.将权限字符串集合  转换成前端需要的权限字符串集合
        data.put("perms",toAPI(permset));
        //7.返回
        return ResponseUtil.ok(data);
    }

    /**
     * 退出方法
     */

   @RequestMapping("logout")
        public Object logout(){
            Subject subject = SecurityUtils.getSubject();
            subject.logout();
            return ResponseUtil.ok();
        }
    /**
     * 将权限字符串集合转换成api形式的权限集合
     * ；例如：admin:brand:list
     */
    private Collection<String> toAPI(Set<String> permSet){
        //封装所有的Collection方法 这些方法应该具有什么样的权限才能够访问
        //systemPermMap中的数据例如: key是admin:brand:list value: POST /admin/brand/list
        if( systemPermMap==null||systemPermMap.size()==0 ){
            systemPermMap=new HashMap<>();
            //设置我们contller所在的包名
            String basicPackage="com.qf";
            //通过反射机制 获取指定包中的contller中带自定义权限注解的方法
            List<Permission> systemPersionList = PermissionUtil.listPermission(context, basicPackage);
            //遍历
            for (Permission permission : systemPersionList) {
                //权限字符串数组
                String[] perm = permission.getRequiresPermissions().value();
                //对应的能够访问的url路径
                String api = permission.getApi();
                ///Key是权限字符串 value是能够访问的路径
                systemPermMap.put(perm[0],api);
            }


        }

        //封装用来存储当前用户能够访问的url地址
        //例如post请求：  POST/admin/admin/delete
        Collection<String> apis = new HashSet<>();

        //遍历传入的这个登录的用户权限字符串集合
        for (String perm : permSet) {
            //根据权限字符串 获取对应的url访问路径
            String api = systemPermMap.get(perm);
            apis.add(api);
            if(perm.equals("*")){
                apis.clear();
                apis.add("*");
                return apis;
            }
        }

        //返回
        return apis;
    }
}
